package fractal;

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.Image;
import java.io.*;
import javax.imageio.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.filechooser.FileNameExtensionFilter;

public class FractalExplorer {

	private int              dispSize;   // display size
	private JImageDisplay    display;    // display JComponent
	private FractalGenerator gen;        // fractal generator
	private FractalGenerator MandelGen;  // Mandelbrot set generator
	private FractalGenerator TriconGen;  // Tricon generator
	private FractalGenerator BurshipGen; // Burship generator
	private FractalGenerator JulierGen;  // Julier set generator
	private Rectangle2D.Double range;    // range of complex plane
	private int  xlimit;                 // limit in x direction of display
	private int  ylimit;                 // limit in y direction of display
	private JComboBox fractalTypeComboBox; 
	private JButton   resetButton;
	private JButton   saveImageButton;
	private int       rowsRemaining;    // remaing rows to be drawed
	
	
	public FractalExplorer(int dispSize) {
		xlimit = ylimit = dispSize;
		this.dispSize = dispSize;
		display = new JImageDisplay(xlimit, ylimit);
		MandelGen  = new Mandelbrot();
		TriconGen  = new Tricon();
		BurshipGen = new BurningShip();
		JulierGen  = new Julier();
		gen = MandelGen;
		range = new Rectangle2D.Double();
		gen.getInitialRange(range);
	} 
	
	
	public void createAndShowGUI() {
		final JFrame f = new JFrame("Fractal viewer");
		f.setLayout(new BorderLayout());
		f.add(display, BorderLayout.CENTER);
		
		display.addMouseListener(new MouseAdapter() {
			public void mouseClicked(MouseEvent e) {
				if (rowsRemaining > 0) return;  // not complete recenter yet
				double centerX = FractalGenerator.getCoord(range.x,
						range.x + range.width, dispSize, e.getX());
				double centerY = FractalGenerator.getCoord(range.y,
						range.y + range.width, dispSize, e.getY());
				gen.recenterAndZoomRange(range, centerX, centerY, 0.5);
				drawFractal();
			}
		});
		
		fractalTypeComboBox = new JComboBox();
		fractalTypeComboBox.addItem(MandelGen);
		fractalTypeComboBox.addItem(TriconGen);
		fractalTypeComboBox.addItem(BurshipGen);
		fractalTypeComboBox.addItem(JulierGen);
		JPanel choosePanel = new JPanel();
		choosePanel.setLayout(new FlowLayout());
		choosePanel.add(new JLabel("Fractal:"));
		choosePanel.add(fractalTypeComboBox);
		
		fractalTypeComboBox.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent e) {
				gen = (FractalGenerator)e.getItem();
				gen.getInitialRange(range);
				drawFractal();					
			}
		});
		
		f.add(choosePanel, BorderLayout.NORTH);
				
		resetButton = new JButton("Reset Display");
		resetButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				gen.getInitialRange(range);
				drawFractal();				
			}
			
		});
		saveImageButton = new JButton("Save Image");
		saveImageButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				JFileChooser chooser = new JFileChooser();
				FileNameExtensionFilter filter = new FileNameExtensionFilter("PNG Images", "png");
				chooser.setFileFilter(filter);
				chooser.setAcceptAllFileFilterUsed(false);
				
				if (chooser.showSaveDialog(f) ==  JFileChooser.APPROVE_OPTION){
					File file = chooser.getSelectedFile();
					saveImageToFile(file, f);
				}
				
			}
		});
		
		JPanel controlPanel = new JPanel();
		controlPanel.setLayout(new FlowLayout());
		controlPanel.add(resetButton);
		controlPanel.add(saveImageButton);
		f.add(controlPanel, BorderLayout.SOUTH);
		
		f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		f.pack();
		f.setVisible(true);
		f.setResizable(false);	
		
	}  // end  createAndShowGUI()
	
	
	/*
	 * Enable or disable GUI according to val
	 * if Val is true, then enable GUI
	 * otherwise disable GUI
	 */
	private void enableUI(boolean val) {
		
			resetButton.setEnabled(val);
			saveImageButton.setEnabled(val);
			fractalTypeComboBox.setEnabled(val);
		
	}
	
	/*
	 * Save image to file using javax.imageio
	 */
	protected void saveImageToFile(File file, JFrame f) {
		try {
			javax.imageio.ImageIO.write(display.getImage(), "png", file);
		} catch (IOException e) {
			JOptionPane.showMessageDialog(f, e.getMessage(), "Cannot Save Image", JOptionPane.ERROR_MESSAGE);
		}
	}


	private void drawFractal() {
		enableUI(false);
		rowsRemaining = ylimit;		
		FractalWorker[] workers = new FractalWorker[ylimit]; // make workers array
		for(int i = 0; i < ylimit; i++) {
			workers[i] = new FractalWorker(i); // make a worker
			workers[i].execute();
		}
	}
	
		
	/*
	 * make FractalExplorer using multi-thread
	 * this create a FractalWorker inner class of SwingWorker 
	 */
	private class FractalWorker extends SwingWorker<Object, Object> {
		private int y;             // y coordinate for row to compute
		private int[] rgbColorRow; // rgb color value row
			
		public FractalWorker(int y) {
			this.y = y;            // y is which row to compute
		}
						
		@Override
		public Object doInBackground() throws Exception {
			rgbColorRow = new int[xlimit];
			double yCoord = FractalGenerator.getCoord(range.y,
					range.y + range.width, dispSize, y);
			
			for(int i = 0; i < xlimit; i++) {
				double xCoord = FractalGenerator.getCoord(range.x,
						range.x + range.width, dispSize, i);
				int numIters = gen.numIterations(xCoord, yCoord);
				if (numIters == -1) 
					rgbColorRow[i] = 0;
				else {
					float hue = 0.7f + (float) numIters / 200f;
					rgbColorRow[i] = Color.HSBtoRGB(hue, 1f, 1f);
				}
			}
			
			return null;
		}
		
		@Override
		public void done() {
			for (int i = 0; i < xlimit; i++) {
				display.drawPixel(i, y, rgbColorRow[i]);
			}
			display.repaint(0, 0, y, xlimit, 1);
			rowsRemaining--;
			if(rowsRemaining == 0) enableUI(true);
		}
	} // end private class FractalWorker
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// set up look and feel
		try {  
            UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");     
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
		// let's go!
		FractalExplorer explorer = new FractalExplorer(600);
		explorer.createAndShowGUI();
		explorer.drawFractal();
	}

}
